package br.ufpe.cin.if680.lightmirror.core;

import java.awt.Color;

public class Retalho {
	protected Ponto p1;
	protected Ponto p2;
	protected Ponto p3;
	protected Vetor cor1;
	protected Vetor cor2;
	protected Vetor cor3;
	protected Triangulo triangulo;//triangulo original
	protected Parametros parametros;
	
	Retalho(Triangulo t, Ponto a, Ponto b, Ponto c, Parametros p){
		this.p1 = a;
		this.p2 = b;
		this.p3 = c;
	    this.triangulo = t;
	    this.parametros = p;
	    this.calcularCorVertices(t);
	}
	
	/**
	 * Calcula a cor do pixel dado por coordBaric.
	 * @param coordBaric
	 */
	protected Color calcularCorPixel(Vetor coordBaric){
	    double xCor = 0.0;
		double yCor = 0.0;
		double zCor = 0.0;
		
		xCor = coordBaric.getX()* (this.cor1.getX());
		xCor += coordBaric.getY()* (this.cor2.getX());
		xCor += coordBaric.getZ()* (this.cor3.getX());
		
		yCor = coordBaric.getX() * (this.cor1.getY());
		yCor += coordBaric.getY() * (this.cor2.getY());
		yCor += coordBaric.getZ() * (this.cor3.getY());
		
		zCor = coordBaric.getX() * (this.cor1.getZ());
		zCor += coordBaric.getY() * (this.cor2.getZ());
		zCor += coordBaric.getZ() * (this.cor3.getZ());
		
		
		if(xCor < 0) xCor = 0;
		if(yCor < 0) yCor = 0;
		if(zCor < 0) zCor = 0;
		//parametros.getGrafico().setColor(new Color((int) Math.min(xCor, 255),(int) Math.min(yCor, 255),(int)Math.min(zCor, 255)));
		return new Color((int) Math.min(xCor, 255),(int) Math.min(yCor, 255),(int)Math.min(zCor, 255));
	}
	
	/**
	 * Calcula a cor do vértice dado pelo vetor coordBaric.
	 * @param coordBaric
	 * @param triangulo
	 * @return
	 */
	protected Vetor calcularCorVertice(Vetor coordBaric, Triangulo triangulo){
		Iluminacao iluminacao = new Iluminacao(this.parametros);
		iluminacao.iluminar(coordBaric, triangulo);
		Vetor cor = new Vetor(0, 0, 0);
		for(int j = 0; j < parametros.nPontosLuz; j++){
			cor = cor.soma(iluminacao.calcularLuz(j));
		}
		return cor;
	}
	
	/**
	 * Calcula a cor dos vértices deste retalho.
	 * @param triangulo
	 */
	protected void calcularCorVertices(Triangulo triangulo){
	     this.cor1 = this.calcularCorVertice(new Vetor(1,0,0),triangulo);
	     this.cor2 = this.calcularCorVertice(new Vetor(0,1,0),triangulo);
	     this.cor3 = this.calcularCorVertice(new Vetor(0,0,1),triangulo);
	}
	
	/**
	 * Retorna a combinação baricêntrica do ponto p em relação a este retalho.
	 */
	public Vetor combinacaoBaricentrica(Ponto p){
		double alpha, beta;
		double a2x = p1.getX() - p3.getX();//ax - cx;
	    double a2y = p1.getY() - p3.getY();//ay - cy;
	    double b2x = p2.getX() - p3.getX();//bx - cx;
	    double b2y = p2.getY() - p3.getY();//by - cy;
	    double p2x = p.getX() - p3.getX();//px - cx;
	    double p2y = p.getY() - p3.getY();//py - cy;

	    if (a2x != 0.0){
	        if ((b2y-((b2x*a2y)/a2x)) != 0.0){
	            beta = ((p2y-((p2x*a2y)/a2x))/(b2y-((b2x*a2y)/a2x)));
	            alpha = ((p2x-(beta*b2x))/a2x);
	        } else {
	            beta = 1.0/3.0;
	            alpha = ((p2x-(beta*b2x))/a2x);
	        }
	    } else {
	        if (b2x != 0.0){
	            beta = p2x/b2x;
	            if (a2y != 0.0) alpha = (p2y-(beta*b2y))/a2y;
	            else alpha = (1 - beta)/2.0;
	        } else {
	            if (a2y != 0.0){
	                beta = 1.0/3.0;
	                alpha = ((p2y-(beta*b2y))/a2y);
	            } else if (b2y != 0.0){
	                alpha = 1.0/3.0;
	                beta = p2y/b2y;
	            } else alpha = beta = 1.0/3.0;
	        }
	    }

	    double gama = (1.0 - alpha - beta);
		return new Vetor(alpha, beta, gama);
	}
	
	/**
	 * Retorna o ponto real de coordenadas baricentricas v.
	 */
	public Ponto coordenadas(Vetor v){
		//Ponto t1 = (triangulo.a.produtoEscalar(v.getX()));
		//Ponto t2 = (triangulo.b.produtoEscalar(v.getY()));
		//Ponto t3 = (triangulo.c.produtoEscalar(v.getZ()));
		return (triangulo.a.produtoEscalar(v.getX()))
		.soma((triangulo.b.produtoEscalar(v.getY()))
				.soma((triangulo.c.produtoEscalar(v.getZ()))));
	}
	
	//XXX Reimplementar
	protected int[] deixarEmOrdem(int x1, int y1, int x2, int y2, int x3, int y3) {
		int tempX, tempY;

		if (y2 > y3) {
			tempX = x2;
			tempY = y2;
			x2 = x3;
			y2 = y3;
			x3 = tempX;
			y3 = tempY;
		}
		if (y1 > y2) {
			tempX = x1;
			tempY = y1;
			x1 = x2;
			y1 = y2;
			x2 = tempX;
			y2 = tempY;
		} else if ((y1 == y2) && (x1 > x2)) {
			tempX = x1;
			tempY = y1;
			x1 = x2;
			y1 = y2;
			x2 = tempX;
			y2 = tempY;
		}
		if (y2 > y3) {
			tempX = x2;
			tempY = y2;
			x2 = x3;
			y2 = y3;
			x3 = tempX;
			y3 = tempY;
		} else if ((y2 == y3) && (x2 > x3)) {
			tempX = x2;
			tempY = y2;
			x2 = x3;
			y2 = y3;
			x3 = tempX;
			y3 = tempY;
		}
		if ((y1 == y2) && (x1 > x2)) {
			tempX = x1;
			tempY = y1;
			x1 = x2;
			y1 = y2;
			x2 = tempX;
			y2 = tempY;
		}

		return new int[] {x1, y1, x2, y2, x3, y3};
	}
	
	protected void pintarScan(){
		//nao deixar pintar triangulos q estao fora da tela
		if((this.p1.x < -1 && this.p2.x < -1 && this.p3.x < -1) ||(this.p1.x > 1 &&  this.p2.x > 1  &&  this.p3.x > 1)||(this.p1.y < -1 && this.p2.y < -1 && this.p3.y < -1)||(this.p1.y > 1 &&  this.p2.y > 1  && this.p3.y > 1)){
			return;
		}
		int x1 = (int) (this.p1.x * parametros.getLargura()/2);
		int y1 = (int) (this.p1.y * parametros.getAltura()/2);
		int x2 = (int) (this.p2.x * parametros.getLargura()/2);
		int y2 = (int) (this.p2.y * parametros.getAltura()/2);
		int x3 = (int) (this.p3.x * parametros.getLargura()/2);
		int y3 = (int) (this.p3.y * parametros.getAltura()/2);

		int yscan;
		int ymax;
		int xscan;
	    double xmin;
	    double xmax;
	    double amin;
	    double amax;

	    int[] novaOrdem = this.deixarEmOrdem(x1, y1, x2, y2, x3, y3);
	    x1 = novaOrdem[0];
	    y1 = novaOrdem[1];
	    x2 = novaOrdem[2];
	    y2 = novaOrdem[3];
	    x3 = novaOrdem[4];
	    y3 = novaOrdem[5];

	        if (y1 != y2){ //Triangulo com base inferior nao reta
	            yscan = y1;
	            ymax = y2;
	            xscan = x1;
	            xmin = x1;
	            xmax = x1;

	            if ((((double) (x2-x1))/(y2 - y1)) < (((double) (x3-x1))/(y3 - y1))){
	                amin = (((double) (x2-x1))/(y2 - y1));
	                amax = (((double) (x3-x1))/(y3 - y1));
	            } else {
	                amin = (((double) (x3-x1))/(y3 - y1));
	                amax = (((double) (x2-x1))/(y2 - y1));
	            }

	            for (yscan = y1; yscan <= ymax; yscan++){
	                for(xscan = (int) xmin; xscan <= xmax; xscan++){
	                	Vetor coordBaric = combinacaoBaricentrica(new Ponto(2 * xscan / parametros.getLargura(), 2 * yscan / parametros.getAltura()));
	                	if(this.consultarBuffer(xscan, yscan, coordBaric)){
	                		parametros.getGrafico().setColor(calcularCorPixel(coordBaric));
							parametros.getGrafico().drawRect((int) (xscan + parametros.getLargura()/2), (int) (parametros.getAltura() - (yscan + parametros.getAltura()/2)), 1, 1);
						}
	                }
	                xmin += amin;
	                xmax += amax;
	            }
	            xmin -= amin;
	            xmax -= amax;

	            ymax = y3;
				yscan--;

	            if ((((double) (x2-x1))/(y2 - y1)) < (((double) (x3-x1))/(y3 - y1))){
	                amin = (((double) (x3-x2))/(y3 - y2));
	                amax = (((double) (x3-x1))/(y3 - y1));
	            } else {
	                amin = (((double) (x3-x1))/(y3 - y1));
	                amax = (((double) (x3-x2))/(y3 - y2));
	            }
	        } else {
	            xscan = x1;
	            xmax = x2;
	            xmin = x1;
	            yscan = y1;
	            ymax = y3;
	            
	            amin = (((double) (x3-x1))/(y3 - y1));
	            amax = (((double) (x3-x2))/(y3 - y2));
	        }

	        if (y2 != y3){
	            for ( ; yscan <= ymax; yscan++){
	                for(xscan = (int) xmin; xscan <= xmax; xscan++){
	                	Vetor coordBaric = combinacaoBaricentrica(new Ponto(2 * xscan / parametros.getLargura(), 2 * yscan / parametros.getAltura()));
						if(this.consultarBuffer(xscan, yscan, coordBaric)) {
							parametros.getGrafico().setColor(calcularCorPixel(coordBaric));
							parametros.getGrafico().drawRect((int) (xscan + parametros.getLargura()/2), (int) (parametros.getAltura() - (yscan + parametros.getAltura()/2)), 1, 1);
						}
	                }
	                xmin += amin;
	                xmax += amax;
	            }
	        }

	        if ((y1 == y2) && (y2 == y3)){
	            yscan = y1;
	            for (xscan = x1; xscan <= x3; xscan++){
	            	Vetor coordBaric = combinacaoBaricentrica(new Ponto(2 * xscan / parametros.getLargura(), 2 * yscan / parametros.getAltura()));
					if(this.consultarBuffer(xscan, yscan, coordBaric)) {
						parametros.getGrafico().setColor(calcularCorPixel(coordBaric));
	                	parametros.getGrafico().drawRect((int) (xscan + parametros.getLargura()/2), (int) (parametros.getAltura() - (yscan + parametros.getAltura()/2)), 1, 1);
					}
	            }
	        }
	}
	
	boolean consultarBuffer(int x, int y, Vetor coordBaric){
		int m = (int) (x + parametros.getLargura()/2);//pois x e y podem ser negativos e os indices do array nao
		int n = (int) (y + parametros.getAltura()/2);
		if(m >= 0 && m < parametros.getLargura() && n >= 0 && n < parametros.getAltura()){//ignora os pixeis fora da tela
			double z = this.coordenadas(coordBaric).getZ();
			if(z >= parametros.getD() ){//z está na frente da camera
				if(parametros.getZbuffer()[m][n] == 0.0){//o buffer nao foi usado ainda pra tal pixel, pintar e setar o buffer
					parametros.setZbuffer(m, n, z);
					return true;
				}else if(z <= parametros.getZbuffer()[m][n]){//se o z a ser pintado tiver ordenada z menor que do buffer, pintar e setar o buffer
					parametros.setZbuffer(m, n, z);
					return true;
				}
			}
		}
		return false;
	}
}
